package za.ac.wits.elen7045.persistence.dao;

import java.util.LinkedList;
import java.util.List;
import za.ac.wits.elen7045.persistence.Environment;
import za.ac.wits.elen7045.persistence.datasource.DataSource;
import za.ac.wits.elen7045.persistence.datasource.DataSourceFactory;
import za.ac.wits.elen7045.persistence.exception.DaoException;
import za.ac.wits.elen7045.persistence.exception.DataSourceProcessException;
import za.ac.wits.elen7045.persistence.model.Account;
import za.ac.wits.elen7045.persistence.model.Credential;
import za.ac.wits.elen7045.persistence.model.ServiceProvider;
import za.ac.wits.elen7045.persistence.model.ServiceProviderName;
import za.ac.wits.elen7045.persistence.util.SQLQueries;

public class SqlServiceProviderDAO implements SqlDao {

	private DataSource<ServiceProvider> servProvDSource;
	private DataSource<Credential> servProvCredDSource;
	
	/** 
	 * Constructor allows package access ONLY to force the use of the factory 
	 * */
	SqlServiceProviderDAO(Environment env) {
		servProvDSource = DataSourceFactory.getDataSource(ServiceProvider.class, env);
		servProvCredDSource = DataSourceFactory.getDataSource(Credential.class, env);
	}
	
	public ServiceProvider getServiceProvider(Account account) {
		try {
			servProvDSource.open();
			servProvCredDSource.open();
			/* get service providers from source */
			ServiceProvider serviceProvider = servProvDSource.retrieve(SQLQueries.getServiceProvider(account)).iterator().next();
			if (serviceProvider != null) {
				serviceProvider.setRequiredCredentials(getRequiredCredentials(serviceProvider));
			}
			return serviceProvider;
		} catch (Exception e) {
			throw new DaoException("Unable to load service provider from database!");
		} finally {
			servProvCredDSource.close();
			servProvDSource.close();
		}
	}
	
	public ServiceProvider getServiceProvider(ServiceProvider serviceProvider) {
		try {
			servProvDSource.open();
			servProvCredDSource.open();
			/* get service providers from source */
			serviceProvider = servProvDSource.retrieve(SQLQueries.getServiceProvider(serviceProvider)).iterator().next();
			if (serviceProvider != null) {
				serviceProvider.setRequiredCredentials(getRequiredCredentials(serviceProvider));
			}
			return serviceProvider;
		} catch (Exception e) {
			throw new DaoException("Unable to load service provider from database!");
		} finally {
			servProvCredDSource.close();
			servProvDSource.close();
		}
	}
	
	public ServiceProvider getServiceProvider(ServiceProviderName serviceProviderType) {
		try {
			servProvDSource.open();
			servProvCredDSource.open();
			/* get service providers from source */
			ServiceProvider serviceProvider = servProvDSource.retrieve(SQLQueries.getServiceProvider(serviceProviderType)).iterator().next();
			if (serviceProvider != null) {
				serviceProvider.setRequiredCredentials(getRequiredCredentials(serviceProvider));
			}
			return serviceProvider;
		} catch (Exception e) {
			throw new DaoException("Unable to load service provider from database!");
		} finally {
			servProvCredDSource.close();
			servProvDSource.close();
		}
	}
	
	public List<ServiceProvider> getServiceProviders() {
		try {
			servProvDSource.open();
			servProvCredDSource.open();
			String query = SQLQueries.getServiceProviders();
			List<ServiceProvider> serviceProviders = new LinkedList<ServiceProvider>();
			/* get service providers from source */
			for (ServiceProvider serviceProvider : servProvDSource.retrieve(query)) {
				serviceProviders.add(serviceProvider);
			}
			/* Get associated credentials for each service provider: 
			 * NOTE: 
			 * 		By making another call to source inside the above for-loop may result in 
			 * 		some database crashes and are therefore written as separated actions... 
			 */
			for (ServiceProvider serviceProvider : serviceProviders) {
				serviceProvider.setRequiredCredentials(getRequiredCredentials(serviceProvider));
			}
			return serviceProviders;
		} catch (Exception e) {
			throw new DaoException("Unable to load service providers from database!");
		} finally {
			servProvCredDSource.close();
			servProvDSource.close();
		}
	}
	
	private List<Credential> getRequiredCredentials(ServiceProvider serviceProvider) {
		try {
			servProvCredDSource.open();
			String query = SQLQueries.getRequiredCredentials(serviceProvider);
			List<Credential> credentials = new LinkedList<Credential>();
			for (Credential credential : servProvCredDSource.retrieve(query)) {
				credentials.add(credential);
			}
			return credentials;
		} catch (Exception e) {
			throw new DataSourceProcessException("Unable to load required credentials from database!");
		} finally {
			servProvCredDSource.close();
		}
	}

	public boolean serviceProviderExist(ServiceProvider serviceProvider) {
		try {
			servProvDSource.open();
			String query = SQLQueries.serviceProviderExist(serviceProvider);
			return servProvDSource.retrieve(query).iterator().hasNext();
		} catch (Exception e) {
			throw new DataSourceProcessException("Unable to verify service provider in database!");
		} 
		finally {
			servProvDSource.close();
		}
	}
	
	public boolean requiredCredentialExist(ServiceProvider serviceProvider, Credential credential) {
		try {
			servProvDSource.open();
			String query = SQLQueries.requiredCredentialExist(serviceProvider, credential);
			return servProvDSource.retrieve(query).iterator().hasNext();
		} catch (Exception e) {
			throw new DataSourceProcessException("Unable to verify required credential in database!");
		} 
		finally {
			servProvDSource.close();
		}
	}
	
	public void addServiceProvider(ServiceProvider serviceProvider) {
		if (serviceProviderExist(serviceProvider)) {
			throw new DaoException("Service provider with name '"+ serviceProvider.getName() +"' already exist!");
		}
		try {
			servProvDSource.open();
			/* Step 1 - add new account */
			servProvDSource.submit(SQLQueries.addServiceProvider(serviceProvider));
			/* Step 2 - get "updated" copy of account (with ID) */
			serviceProvider = servProvDSource.retrieve(SQLQueries.getServiceProvider(serviceProvider)).iterator().next();
			servProvDSource.commit();
			/* Step 4 - add account credentials */
			List<Credential> credentials = serviceProvider.getRequiredCredentials();
			if (credentials != null && !credentials.isEmpty()) {
				addRequiredCredentials(serviceProvider, credentials);
			}
		} catch (Exception e) {
			e.printStackTrace();
			throw new DaoException("Unable to add service provider to database!");
		} 
		finally {
			servProvDSource.close();
		}
	}
	
	public void removeServiceProvider(ServiceProvider serviceProvider) {
		if (!serviceProviderExist(serviceProvider)) {
			throw new DaoException("Service provider with name '"+ serviceProvider.getName() +"' doesn't exist!");
		}
		try {
			servProvDSource.open();
			servProvDSource.submit(SQLQueries.removeServiceProvider(serviceProvider));
			/* Step 1 - get "updated" copy of account (with ID) */
			serviceProvider = servProvDSource.retrieve(SQLQueries.getServiceProvider(serviceProvider)).iterator().next();
			servProvDSource.commit();
			/* Step 2 - remove credentials */
			List<Credential> credentials = serviceProvider.getRequiredCredentials();
			if (credentials != null && !credentials.isEmpty()) {
				removeRequiredCredentials(serviceProvider, credentials);
			}
		} catch (Exception e) {
			e.printStackTrace();
			throw new DaoException("Unable to remove service provider from database!");
		} 
		finally {
			servProvDSource.close();
		}
	}
	
	public void addRequiredCredential(ServiceProvider serviceProvider, Credential credential) {
		if (requiredCredentialExist(serviceProvider, credential)) {
			throw new DaoException("Required credential with name '"+ credential.getName() +"' already exist!");
		}
		try {
			servProvCredDSource.open();
			servProvCredDSource.submit(SQLQueries.addRequiredCredential(serviceProvider, credential));
			servProvCredDSource.commit();
		} catch (Exception e) {
			throw new DaoException("Unable to add required credential to service provider in database!");
		} 
		finally {
			servProvCredDSource.close();
		}
	}
	
	public void removeRequiredCredential(ServiceProvider serviceProvider, Credential credential) {
		if (!requiredCredentialExist(serviceProvider, credential)) {
			throw new DaoException("Required credential with name '"+ credential.getName() +"' doesn't exist!");
		}
		try {
			servProvCredDSource.open();
			servProvCredDSource.submit(SQLQueries.removeRequiredCredential(serviceProvider, credential));
			servProvCredDSource.commit();
		} catch (Exception e) {
			throw new DaoException("Unable to remove required credential from service provider in database!");
		} 
		finally {
			servProvCredDSource.close();
		}
	}
	
	public void addRequiredCredentials(ServiceProvider serviceProvider, List<Credential> credentials) {
		try {
			servProvCredDSource.open();
			for (Credential credential : credentials) {
				if (requiredCredentialExist(serviceProvider, credential)) {
					throw new DaoException("Required credential with name '"+ credential.getName() +"' already exist!");
				}
				servProvCredDSource.submit(SQLQueries.addRequiredCredential(serviceProvider, credential));
			}
		} catch (Exception e) {
			e.printStackTrace();
			throw new DaoException("Unable to add required credential to service provider in database!");
		} 
		finally {
			servProvCredDSource.close();
		}
	}
	
	public void removeRequiredCredentials(ServiceProvider serviceProvider, List<Credential> credentials) {
		try {
			servProvCredDSource.open();
			for (Credential credential : credentials) {
				if (!requiredCredentialExist(serviceProvider, credential)) {
					throw new DaoException("Required credential with name '"+ credential.getName() +"' doesn't exist!");
				}
				servProvCredDSource.submit(SQLQueries.removeRequiredCredential(serviceProvider, credential));
			}
		} catch (Exception e) {
			throw new DaoException("Unable to add required credential to service provider in database!");
		} 
		finally {
			servProvCredDSource.close();
		}
	}
	
}
